2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 #import "ESPurpleJabberAccount.h"
18 #import <AdiumLibpurple/SLPurpleCocoaAdapter.h>
19 #import <Adium/AIAccountControllerProtocol.h>
20 #import <Adium/AIInterfaceControllerProtocol.h>
21 #import <Adium/AIStatusControllerProtocol.h>
22 #import <Adium/AIContactControllerProtocol.h>
23 #import <Adium/AIChat.h>
24 #import <Adium/AIHTMLDecoder.h>
25 #import <Adium/AIListContact.h>
26 #import <Adium/AIStatus.h>
27 #import <Adium/ESFileTransfer.h>
28 #import <Adium/ESTextAndButtonsWindowController.h>
29 #import <Adium/AIService.h>
30 #import <AIUtilities/AIApplicationAdditions.h>
31 #import <AIUtilities/AIAttributedStringAdditions.h>
32 #import <AIUtilities/AIStringAdditions.h>
33 #include <libpurple/presence.h>
34 #include <libpurple/si.h>
35 #include <SystemConfiguration/SystemConfiguration.h>
36 #import "AMXMLConsoleController.h"
37 #import "AMPurpleJabberServiceDiscoveryBrowsing.h"
38 #import "ESPurpleJabberAccountViewController.h"
39 #import "AMPurpleJabberAdHocServer.h"
40 #import "AMPurpleJabberAdHocPing.h"
43 #import "AIPurpleCertificateViewer.h"
46 #define DEFAULT_JABBER_HOST @"@jabber.org"
48 @interface ESPurpleJabberAccount (PRIVATE)
49 - (BOOL)enableXMLConsole;
52 @implementation ESPurpleJabberAccount
55 * @brief The UID will be changed. The account has a chance to perform modifications
57 * Upgrade old Jabber accounts stored with the host in a separate key to have the right UID, in the form
60 * Append @jabber.org to a proposed UID which has no domain name and does not need to be updated.
62 * @param proposedUID The proposed, pre-filtered UID (filtered means it has no characters invalid for this servce)
63 * @result The UID to use; the default implementation just returns proposedUID.
65 - (NSString *)accountWillSetUID:(NSString *)proposedUID
67 proposedUID = [proposedUID lowercaseString];
70 if ((proposedUID && ([proposedUID length] > 0)) &&
71 ([proposedUID rangeOfString:@"@"].location == NSNotFound)) {
74 //Upgrade code: grab a previously specified Jabber host
75 if ((host = [self preferenceForKey:@"Jabber:Host" group:GROUP_ACCOUNT_STATUS ignoreInheritedValues:YES])) {
76 //Determine our new, full UID
77 correctUID = [NSString stringWithFormat:@"%@@%@",proposedUID, host];
79 //Clear the preference and then set the UID so we don't perform this upgrade again
80 [self setPreference:nil forKey:@"Jabber:Host" group:GROUP_ACCOUNT_STATUS];
81 [self setPreference:correctUID forKey:@"FormattedUID" group:GROUP_ACCOUNT_STATUS];
84 //Append [self serverSuffix] (e.g. @jabber.org) to a Jabber account with no server
85 correctUID = [proposedUID stringByAppendingString:[self serverSuffix]];
88 correctUID = proposedUID;
94 - (const char*)protocolPlugin
101 [xmlConsoleController close];
102 [xmlConsoleController release];
107 - (NSSet *)supportedPropertyKeys
109 static NSMutableSet *supportedPropertyKeys = nil;
111 if (!supportedPropertyKeys) {
112 supportedPropertyKeys = [[NSMutableSet alloc] initWithObjects:
116 [supportedPropertyKeys unionSet:[super supportedPropertyKeys]];
119 return supportedPropertyKeys;
122 - (void)configurePurpleAccount
124 [super configurePurpleAccount];
126 NSString *connectServer;
127 BOOL forceOldSSL, allowPlaintext, requireTLS;
129 purple_account_set_username(account, [self purpleAccountName]);
131 //'Connect via' server (nil by default)
132 connectServer = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS];
133 //XXX - As of libpurple 2.0.0, 'localhost' doesn't work properly by 127.0.0.1 does. Hack!
134 if (connectServer && [connectServer isEqualToString:@"localhost"])
135 connectServer = @"127.0.0.1";
137 purple_account_set_string(account, "connect_server", (connectServer ?
138 [connectServer UTF8String] :
141 //Force old SSL usage? (off by default)
142 forceOldSSL = [[self preferenceForKey:KEY_JABBER_FORCE_OLD_SSL group:GROUP_ACCOUNT_STATUS] boolValue];
143 purple_account_set_bool(account, "old_ssl", forceOldSSL);
145 //Require SSL or TLS? (off by default)
146 requireTLS = [[self preferenceForKey:KEY_JABBER_REQUIRE_TLS group:GROUP_ACCOUNT_STATUS] boolValue];
147 purple_account_set_bool(account, "require_tls", requireTLS);
149 //Allow plaintext authorization over an unencrypted connection? Purple will prompt if this is NO and is needed.
150 allowPlaintext = [[self preferenceForKey:KEY_JABBER_ALLOW_PLAINTEXT group:GROUP_ACCOUNT_STATUS] boolValue];
151 purple_account_set_bool(account, "auth_plain_in_clear", allowPlaintext);
153 /* Mac OS X 10.4's cyrus-sasl gives us problems. Is it a bug in the installed library, a bug in its compilation, or a bug
154 * in our linkage against it? I don't know. In any case, work around it as much as possible by utilizing libpurple's own implementation
155 * of PLAIN and DIGEST-MD5 on 10.4. We can safely use cyrus-sasl in all its authenticating awesomeness for all methods on 10.5. -evands
157 * This preference is added via the "libpurple_jabber_avoid_sasl_option_hack.diff" patch we apply during the build process.
159 purple_prefs_set_bool("/plugins/prpl/jabber/avoid_sasl_for_plain_and_digest_md5_auth", [NSApp isTiger]);
162 - (NSString *)serverSuffix
164 NSString *host = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS];
166 return (host ? host : DEFAULT_JABBER_HOST);
169 /*! @brief Obtain the resource name for this Jabber account.
171 * This could be extended in the future to perform keyword substitution (e.g. s/%computerName%/CSCopyMachineName()/).
173 * @return The resource name for the account.
175 - (NSString *)resourceName
177 NSString *resource = [self preferenceForKey:KEY_JABBER_RESOURCE group:GROUP_ACCOUNT_STATUS];
179 if(resource == nil || [resource length] == 0)
180 resource = [(NSString*)SCDynamicStoreCopyLocalHostName(NULL) autorelease];
185 - (const char *)purpleAccountName
187 NSString *userNameWithHost = nil, *completeUserName = nil;
188 BOOL serverAppendedToUID;
191 * Purple stores the username in the format username@server/resource. We need to pass it a username in this format
193 * The user should put the username in username@server format, which is common for Jabber. If the user does
194 * not specify the server, use jabber.org.
197 serverAppendedToUID = ([UID rangeOfString:@"@"].location != NSNotFound);
199 if (serverAppendedToUID) {
200 userNameWithHost = UID;
202 userNameWithHost = [UID stringByAppendingString:[self serverSuffix]];
205 completeUserName = [NSString stringWithFormat:@"%@/%@" ,userNameWithHost, [self resourceName]];
207 return [completeUserName UTF8String];
211 * @brief Connect Host
213 * Convenience method for retrieving the connect host for this account
215 * Rather than having a separate server field, Jabber uses the servername after the user name.
216 * username@server.org
218 * The connect server, stored in KEY_JABBER_CONNECT_SERVER, overrides this to provide the connect host. It will
219 * not be set in most cases.
225 if (!(host = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS])) {
226 int location = [UID rangeOfString:@"@"].location;
228 if ((location != NSNotFound) && (location + 1 < [UID length])) {
229 host = [UID substringFromIndex:(location + 1)];
232 host = [self serverSuffix];
240 * @brief Should set aliases serverside?
242 * Jabber supports serverside aliases.
244 - (BOOL)shouldSetAliasesServerside
249 - (AIListContact *)contactWithUID:(NSString *)sourceUID
251 AIListContact *contact;
253 contact = [[adium contactController] existingContactWithService:service
257 contact = [[adium contactController] contactWithService:[self _serviceForUID:sourceUID]
265 - (AIService *)_serviceForUID:(NSString *)contactUID
267 AIService *contactService;
268 NSString *contactServiceID = nil;
270 if ([contactUID hasSuffix:@"@gmail.com"] ||
271 [contactUID hasSuffix:@"@googlemail.com"]) {
272 contactServiceID = @"libpurple-jabber-gtalk";
274 } else if([contactUID hasSuffix:@"@livejournal.com"]){
275 contactServiceID = @"libpurple-jabber-livejournal";
278 contactServiceID = @"libpurple-Jabber";
281 contactService = [[adium accountController] serviceWithUniqueID:contactServiceID];
283 return contactService;
286 - (id)authorizationRequestWithDict:(NSDictionary*)dict {
288 switch([[self preferenceForKey:KEY_JABBER_SUBSCRIPTION_BEHAVIOR group:GROUP_ACCOUNT_STATUS] intValue]) {
289 case 2: // always accept + add
292 NSString *groupname = [self preferenceForKey:KEY_JABBER_SUBSCRIPTION_GROUP group:GROUP_ACCOUNT_STATUS];
293 if([groupname length] > 0) {
294 AIListContact *contact = [[adium contactController] contactWithService:[self service] account:self UID:[dict objectForKey:@"Remote Name"]];
295 AIListGroup *group = [[adium contactController] groupWithUID:groupname];
296 [[adium contactController] addContacts:[NSArray arrayWithObject:contact] toGroup:group];
300 case 1: // always accept
301 [[self purpleThread] doAuthRequestCbValue:[[[dict objectForKey:@"authorizeCB"] retain] autorelease] withUserDataValue:[[[dict objectForKey:@"userData"] retain] autorelease]];
302 handle = [[NSObject alloc] init];
304 case 3: // always deny
305 [[self purpleThread] doAuthRequestCbValue:[[[dict objectForKey:@"denyCB"] retain] autorelease] withUserDataValue:[[[dict objectForKey:@"userData"] retain] autorelease]];
306 handle = [[NSObject alloc] init];
308 default: // ask (should be 0)
309 return [super authorizationRequestWithDict:dict];
312 // we can't execute this immediately, since libpurple doesn't know about the handle yet
313 [self performSelector:@selector(closeAuthRequest:) withObject:handle afterDelay:0.0];
317 - (void)closeAuthRequest:(id)handle {
318 purple_account_request_close(handle);
321 - (void)purpleAccountRegistered:(BOOL)success
323 if(success && [[self service] accountViewController]) {
324 const char *usernamestr = account->username;
327 NSString *userWithResource = [NSString stringWithUTF8String:usernamestr];
328 NSRange slashrange = [userWithResource rangeOfString:@"/"];
329 if(slashrange.location != NSNotFound)
330 username = [userWithResource substringToIndex:slashrange.location];
332 username = userWithResource;
334 username = (id)[NSNull null];
336 NSString *pw = (account->password ? [NSString stringWithUTF8String:account->password] : [NSNull null]);
338 [[adium notificationCenter] postNotificationName:AIAccountUsernameAndPasswordRegisteredNotification
340 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
341 username, @"username",
347 #pragma mark Contacts
348 - (void)updateSignon:(AIListContact *)theContact withData:(void *)data
350 [super updateSignon:theContact withData:data];
352 //We only get user icons in Jabber when we request info. Do that now!
353 [self delayedUpdateContactStatus:theContact];
358 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject
360 static AIHTMLDecoder *jabberHtmlEncoder = nil;
361 if (!jabberHtmlEncoder) {
362 jabberHtmlEncoder = [[AIHTMLDecoder alloc] init];
363 [jabberHtmlEncoder setIncludesHeaders:NO];
364 [jabberHtmlEncoder setIncludesFontTags:YES];
365 [jabberHtmlEncoder setClosesFontTags:YES];
366 [jabberHtmlEncoder setIncludesStyleTags:YES];
367 [jabberHtmlEncoder setIncludesColorTags:YES];
368 [jabberHtmlEncoder setEncodesNonASCII:NO];
369 [jabberHtmlEncoder setPreservesAllSpaces:NO];
370 [jabberHtmlEncoder setUsesAttachmentTextEquivalents:YES];
373 return [jabberHtmlEncoder encodeHTML:inAttributedString imagesPath:nil];
376 - (NSString *)_UIDForAddingObject:(AIListContact *)object
378 NSString *objectUID = [object UID];
381 if ([objectUID rangeOfString:@"@"].location != NSNotFound) {
382 properUID = objectUID;
384 properUID = [NSString stringWithFormat:@"%@@%@",objectUID,[self host]];
387 return [properUID lowercaseString];
390 - (NSString *)unknownGroupName {
391 return (AILocalizedString(@"Roster","Roster - the Jabber default group"));
394 - (NSString *)connectionStringForStep:(int)step
398 return AILocalizedString(@"Connecting",nil);
401 return AILocalizedString(@"Initializing Stream",nil);
404 return AILocalizedString(@"Reading data",nil);
407 return AILocalizedString(@"Authenticating",nil);
410 return AILocalizedString(@"Initializing Stream",nil);
413 return AILocalizedString(@"Authenticating",nil);
419 - (AIReconnectDelayType)shouldAttemptReconnectAfterDisconnectionError:(NSString **)disconnectionError
421 AIReconnectDelayType shouldAttemptReconnect = [super shouldAttemptReconnectAfterDisconnectionError:disconnectionError];
423 if (([self lastDisconnectionReason] == PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR) &&
424 ([self shouldVerifyCertificates])) {
425 shouldAttemptReconnect = AIReconnectNever;
428 return shouldAttemptReconnect;
431 - (void)disconnectFromDroppedNetworkConnection
433 /* Before we disconnect from a dropped network connection, set gc->disconnect_timeout to a non-0 value.
434 * This will let the prpl know that we are disconnecting with no backing ssl connection and that therefore
435 * the ssl connection is has should not be messaged in the process of disconnecting.
437 PurpleConnection *gc = purple_account_get_connection(account);
438 if (PURPLE_CONNECTION_IS_VALID(gc) &&
439 !gc->disconnect_timeout) {
440 gc->disconnect_timeout = -1;
441 AILog(@"%@: Disconnecting from a dropped network connection", self);
444 [super disconnectFromDroppedNetworkConnection];
447 - (BOOL)shouldIncludeNowPlayingInformationInAllStatuses
449 return [[self preferenceForKey:KEY_BROADCAST_MUSIC_INFO group:GROUP_ACCOUNT_STATUS] boolValue];
452 #pragma mark File transfer
453 - (BOOL)canSendFolders
458 - (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
460 [super _beginSendOfFileTransfer:fileTransfer];
463 - (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
465 [super acceptFileTransferRequest:fileTransfer];
468 - (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
470 [super rejectFileReceiveRequest:fileTransfer];
473 - (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
475 [super cancelFileTransfer:fileTransfer];
478 #pragma mark Status Messages
479 - (NSAttributedString *)statusMessageForPurpleBuddy:(PurpleBuddy *)b
481 NSAttributedString *statusMessage = nil;
483 if (purple_account_is_connected(account)) {
484 char *normalized = g_strdup(purple_normalize(b->account, b->name));
487 if ((jb = jabber_buddy_find(account->gc->proto_data, normalized, FALSE))) {
488 NSString *statusMessageString = nil;
489 const char *msg = jabber_buddy_get_status_msg(jb);
492 //Get the custom jabber status message if one is set
493 statusMessageString = [NSString stringWithUTF8String:msg];
496 if (statusMessageString && [statusMessageString length]) {
497 statusMessage = [AIHTMLDecoder decodeHTML:statusMessageString];
504 return statusMessage;
507 - (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
509 NSString *statusName = nil;
510 PurplePresence *presence = purple_buddy_get_presence(buddy);
511 PurpleStatus *status = purple_presence_get_active_status(presence);
512 const char *purpleStatusID = purple_status_get_id(status);
514 if (!purpleStatusID) return nil;
516 if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT))) {
517 statusName = STATUS_NAME_FREE_FOR_CHAT;
519 } else if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA))) {
520 statusName = STATUS_NAME_EXTENDED_AWAY;
522 } else if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND))) {
523 statusName = STATUS_NAME_DND;
531 * @brief Jabber status messages are plaintext
533 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forStatusState:(AIStatus *)statusState
535 return [[inAttributedString attributedStringByConvertingLinksToStrings] string];
538 #pragma mark Menu items
539 - (NSString *)titleForContactMenuLabel:(const char *)label forContact:(AIListContact *)inContact
541 if (strcmp(label, "Un-hide From") == 0) {
542 return [NSString stringWithFormat:AILocalizedString(@"Un-hide From %@",nil),[inContact formattedUID]];
544 } else if (strcmp(label, "Temporarily Hide From") == 0) {
545 return [NSString stringWithFormat:AILocalizedString(@"Temporarily Hide From %@",nil),[inContact formattedUID]];
547 } else if (strcmp(label, "Unsubscribe") == 0) {
548 return [NSString stringWithFormat:AILocalizedString(@"Unsubscribe %@",nil),[inContact formattedUID]];
550 } else if (strcmp(label, "(Re-)Request authorization") == 0) {
551 return [NSString stringWithFormat:AILocalizedString(@"Re-request Authorization from %@",nil),[inContact formattedUID]];
553 } else if (strcmp(label, "Cancel Presence Notification") == 0) {
554 return [NSString stringWithFormat:AILocalizedString(@"Cancel Presence Notification to %@",nil),[inContact formattedUID]];
557 return [super titleForContactMenuLabel:label forContact:inContact];
560 - (NSString *)titleForAccountActionMenuLabel:(const char *)label
562 if (strcmp(label, "Set User Info...") == 0) {
563 return [AILocalizedString(@"Set User Info", nil) stringByAppendingEllipsis];
565 } else if (strcmp(label, "Search for Users...") == 0) {
566 return [AILocalizedString(@"Search for Users", nil) stringByAppendingEllipsis];
568 } else if (strcmp(label, "Set Mood...") == 0) {
569 return [AILocalizedString(@"Set Mood", nil) stringByAppendingEllipsis];
571 } else if (strcmp(label, "Set Nickname...") == 0) {
572 return [AILocalizedString(@"Set Nickname", nil) stringByAppendingEllipsis];
575 return [super titleForAccountActionMenuLabel:label];
578 #pragma mark Multiuser chat
580 //Multiuser chats come in with just the contact's name as contactName, but we want to actually do it right.
581 - (NSString *)uidForContactWithUID:(NSString *)inUID inChat:(AIChat *)chat
583 return [NSString stringWithFormat:@"%@/%@",[chat name],inUID];
588 * @brief Return the purple status type to be used for a status
590 * Most subclasses should override this method; these generic values may be appropriate for others.
592 * Active services provided nonlocalized status names. An AIStatus is passed to this method along with a pointer
593 * to the status message. This method should handle any status whose statusNname this service set as well as any statusName
594 * defined in AIStatusController.h (which will correspond to the services handled by Adium by default).
595 * It should also handle a status name not specified in either of these places with a sane default, most likely by loooking at
596 * [statusState statusType] for a general idea of the status's type.
598 * @param statusState The status for which to find the purple status ID
599 * @param arguments Prpl-specific arguments which will be passed with the state. Message is handled automatically.
601 * @result The purple status ID
603 - (const char *)purpleStatusIDForStatus:(AIStatus *)statusState
604 arguments:(NSMutableDictionary *)arguments
606 const char *statusID = NULL;
607 NSString *statusName = [statusState statusName];
608 NSString *statusMessageString = [statusState statusMessageString];
609 NSNumber *priority = nil;
611 if (!statusMessageString) statusMessageString = @"";
613 switch ([statusState statusType]) {
614 case AIAvailableStatusType:
616 if (([statusName isEqualToString:STATUS_NAME_FREE_FOR_CHAT]) ||
617 ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_FREE_FOR_CHAT]] == NSOrderedSame))
618 statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT);
619 priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AVAILABLE group:GROUP_ACCOUNT_STATUS];
623 case AIAwayStatusType:
625 if (([statusName isEqualToString:STATUS_NAME_DND]) ||
626 ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_DND]] == NSOrderedSame))
627 statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND);
628 else if (([statusName isEqualToString:STATUS_NAME_EXTENDED_AWAY]) ||
629 ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_EXTENDED_AWAY]] == NSOrderedSame))
630 statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA);
631 priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AWAY group:GROUP_ACCOUNT_STATUS];
635 case AIInvisibleStatusType:
636 AILog(@"Warning: Invisibility is not yet supported in libpurple 2.0.0 jabber");
637 priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AWAY group:GROUP_ACCOUNT_STATUS];
638 statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_AWAY);
639 // statusID = "Invisible";
642 case AIOfflineStatusType:
646 //Set our priority, which is actually set along with the status...Default is 0.
647 [arguments setObject:(priority ? priority : [NSNumber numberWithInt:0])
650 //We could potentially set buzz on a per-status basis. We have no UI for this, however.
651 [arguments setObject:[NSNumber numberWithBool:YES] forKey:@"buzz"];
653 //If we didn't get a purple status ID, request one from super
654 if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments];
659 #pragma mark Gateway Tracking
661 - (void)updateContact:(AIListContact *)theContact toGroupName:(NSString *)groupName contactName:(NSString *)contactName {
662 NSRange atsign = [[theContact UID] rangeOfString:@"@"];
663 if(atsign.location != NSNotFound)
664 [super updateContact:theContact toGroupName:groupName contactName:contactName];
666 NSEnumerator *e = [gateways objectEnumerator];
667 NSDictionary *gatewaydict;
669 while((gatewaydict = [e nextObject])) {
670 if([[[gatewaydict objectForKey:@"contact"] UID] isEqualToString:[theContact UID]]) {
671 [gateways removeObjectIdenticalTo:gatewaydict];
676 [gateways addObject:[NSDictionary dictionaryWithObjectsAndKeys:
677 theContact, @"contact",
678 groupName, @"remoteGroup",
683 - (void)removeContact:(AIListContact *)theContact {
684 NSRange atsign = [[theContact UID] rangeOfString:@"@"];
685 if(atsign.location != NSNotFound)
686 [super removeContact:theContact];
688 NSEnumerator *e = [gateways objectEnumerator];
689 NSDictionary *gatewaydict;
690 while((gatewaydict = [e nextObject])) {
691 if([[[gatewaydict objectForKey:@"contact"] UID] isEqualToString:[theContact UID]]) {
692 [[self purpleThread] removeUID:[theContact UID] onAccount:self fromGroup:[gatewaydict objectForKey:@"remoteGroup"]];
694 [gateways removeObjectIdenticalTo:gatewaydict];
701 #pragma mark XML Console, Tooltip, AdHoc Server Integration and Gateway Integration
704 * @brief Returns whether or not this account is connected via an encrypted connection.
708 return ([self online] && [self secureConnection]);
712 gateways = [[NSMutableArray alloc] init];
715 [adhocServer release];
716 adhocServer = [[AMPurpleJabberAdHocServer alloc] initWithAccount:self];
717 [adhocServer addCommand:@"ping" delegate:[AMPurpleJabberAdHocPing class] name:@"Ping"];
721 if ([self enableXMLConsole]) {
722 if (!xmlConsoleController) xmlConsoleController = [[AMXMLConsoleController alloc] init];
723 [xmlConsoleController setPurpleConnection:purple_account_get_connection(account)];
726 discoveryBrowserController = [[AMPurpleJabberServiceDiscoveryBrowsing alloc] initWithAccount:self
727 purpleConnection:purple_account_get_connection(account)];
730 - (void)didDisconnect {
731 [xmlConsoleController setPurpleConnection:NULL];
733 [discoveryBrowserController release]; discoveryBrowserController = nil;
734 [adhocServer release]; adhocServer = nil;
736 [super didDisconnect];
738 [gateways release]; gateways = nil;
741 - (IBAction)showXMLConsole:(id)sender {
742 if(xmlConsoleController)
743 [xmlConsoleController showWindow:sender];
748 - (BOOL)enableXMLConsole
752 //Always enable the XML console for debug builds
755 //For non-debug builds, only enable it if the preference is set
756 enableConsole = [[NSUserDefaults standardUserDefaults] boolForKey:@"AMXMPPShowAdvanced"];
759 return enableConsole;
762 - (IBAction)showDiscoveryBrowser:(id)sender {
763 [discoveryBrowserController browse:sender];
766 - (PurpleSslConnection*)secureConnection {
767 // this is really ugly
768 if([self purpleAccount]->gc && [self purpleAccount]->gc->proto_data)
769 return ((JabberStream*)[self purpleAccount]->gc->proto_data)->gsc;
773 - (void)setShouldVerifyCertificates:(BOOL)yesOrNo {
774 [self setPreference:[NSNumber numberWithBool:yesOrNo] forKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS];
777 - (BOOL)shouldVerifyCertificates {
778 return ([self preferenceForKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS]==nil) || [[self preferenceForKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS] boolValue];
782 - (IBAction)showServerCertificate:(id)sender {
783 CFArrayRef certificates = [[self purpleThread] copyServerCertificates:[self secureConnection]];
785 [AIPurpleCertificateViewer displayCertificateChain:certificates forAccount:self];
786 CFRelease(certificates);
790 - (NSArray *)accountActionMenuItems {
791 AILog(@"Getting accountActionMenuItems for %@",self);
792 NSMutableArray *menu = [[NSMutableArray alloc] init];
794 if([gateways count] > 0) {
795 NSEnumerator *e = [gateways objectEnumerator];
796 NSDictionary *gatewaydict;
797 while((gatewaydict = [e nextObject])) {
798 AIListContact *gateway = [gatewaydict objectForKey:@"contact"];
799 NSMenuItem *mitem = [[NSMenuItem alloc] initWithTitle:[gateway UID] action:@selector(registerGateway:) keyEquivalent:@""];
800 NSMenu *submenu = [[NSMenu alloc] initWithTitle:[gateway UID]];
802 NSArray *menuitemarray = [self menuItemsForContact:gateway];
803 NSEnumerator *e2 = [menuitemarray objectEnumerator];
805 while((m2item = [e2 nextObject]))
806 [submenu addItem:m2item];
808 if([submenu numberOfItems] > 0)
809 [submenu addItem:[NSMenuItem separatorItem]];
811 NSMenuItem *removeItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Remove gateway","gateway menu item") action:@selector(removeGateway:) keyEquivalent:@""];
812 [removeItem setTarget:self];
813 [removeItem setRepresentedObject:gateway];
814 [submenu addItem:removeItem];
815 [removeItem release];
817 [mitem setSubmenu:submenu];
819 [mitem setRepresentedObject:gateway];
820 [mitem setImage:[gateway displayArrayObjectForKey:@"Tab Status Icon"]];
821 [mitem setTarget:self];
822 [menu addObject:mitem];
825 [menu addObject:[NSMenuItem separatorItem]];
828 NSArray *supermenu = [super accountActionMenuItems];
830 [menu addObjectsFromArray:supermenu];
831 [menu addObject:[NSMenuItem separatorItem]];
834 if ([self enableXMLConsole]) {
835 NSMenuItem *xmlConsoleMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"XML Console",nil)
836 action:@selector(showXMLConsole:)
838 [xmlConsoleMenuItem setTarget:self];
839 [menu addObject:xmlConsoleMenuItem];
840 [xmlConsoleMenuItem release];
843 NSMenuItem *discoveryBrowserMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Discovery Browser",nil)
844 action:@selector(showDiscoveryBrowser:)
846 [discoveryBrowserMenuItem setTarget:self];
847 [menu addObject:discoveryBrowserMenuItem];
848 [discoveryBrowserMenuItem release];
851 if([self encrypted]) {
852 NSMenuItem *showCertificateMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Show Server Certificate",nil)
853 action:@selector(showServerCertificate:)
855 [showCertificateMenuItem setTarget:self];
856 [menu addObject:showCertificateMenuItem];
857 [showCertificateMenuItem release];
861 return [menu autorelease];
864 - (void)registerGateway:(NSMenuItem*)mitem {
865 if(mitem && [mitem representedObject])
866 jabber_register_gateway((JabberStream*)[self purpleAccount]->gc->proto_data, [[[mitem representedObject] UID] UTF8String]);
871 - (void)removeGateway:(NSMenuItem*)mitem {
872 AIListContact *gateway = [mitem representedObject];
873 if(![gateway isKindOfClass:[AIListContact class]])
875 // since this is a potentially dangerous operation, get a confirmation from the user first
876 if([[NSAlert alertWithMessageText:AILocalizedString(@"Really remove gateway?",nil)
877 defaultButton:AILocalizedString(@"Remove","alert default button")
878 alternateButton:AILocalizedString(@"Cancel",nil)
880 informativeTextWithFormat:AILocalizedString(@"This operation would remove the gateway %@ itself and all contacts belonging to the gateway on your contact list. It cannot be undone.",nil), [gateway UID]] runModal] == NSAlertDefaultReturn) {
881 // first, locate all contacts on the roster that belong to this gateway
882 NSString *jid = [gateway UID];
883 NSString *pattern = [@"@" stringByAppendingString:jid];
884 NSMutableArray *gatewayContacts = [[NSMutableArray alloc] init];
885 NSEnumerator *e = [[[adium contactController] allContactsOnAccount:self] objectEnumerator];
886 AIListContact *contact;
887 while((contact = [e nextObject])) {
888 if([[contact UID] hasSuffix:pattern])
889 [gatewayContacts addObject:contact];
891 // now, remove them from the roster
892 [self removeContacts:gatewayContacts];
894 // finally, remove the gateway itself
895 [self removeContact:gateway];
899 - (AMPurpleJabberAdHocServer*)adhocServer {